﻿@*Creates the layout of the search panel on the far left panel of the app
    Allows the user to search through the File Tree*@

@page "/SearchViewer"
@using Microsoft.Build.Logging.StructuredLogger
@using Radzen
@using Radzen.Blazor
@using System.Collections
@inject IJSRuntime JSRuntime

<i class="fa fa-check"></i>
<RadzenTextBox Value=@SearchKeyword
               Placeholder="Search..."
               Change="@(args => Change(args, "SearchBar"))"
               Style="margin-bottom: 20px"
               aria-label="Search through the File Tree" />
<br />
<br />

@if (String.IsNullOrEmpty(SearchKeyword))
{
    <p style="white-space:normal;word-break:break-word">
        Type in the search box to search. Results (up to 1000) will display here. <br /> <br />
        Search for multiple words separated by space (space means AND). Enclose multiple words in double quotes "" to search for the exact phrase. <br /> <br />
        Use syntax like '$property Prop' to narrow results down by item kind. Supported kinds: $project, $target, $task, $warning, $message, $property, $item, $additem, $removeitem, $metadata, $copytask, $import, $noimport
    </p>
} else
{
    <RadzenTree Data="@searchResults"
                Style="word-break:break-all"
                Change="@OnChange"
                Expand="@((TreeExpandEventArgs args) => { ExpandResults(args); })">
        <RadzenTreeLevel Expanded="@((obj) => (obj is TreeNode && ((TreeNode)obj).HasChildren))"
                         Template="@TreeFormatting.TreeDesign"
                         Text="@TreeFormatting.TextSelector"
                         HasChildren="@((node) =>
                                    {
                                        if (node is TreeNode)
                                            return ((TreeNode)node).HasChildren;
                                        else return false;
                                    })" />
    </RadzenTree>
}

@code {
    [CascadingParameter]
    public SplitPane ContainerSplit { get; set; }
    public static StructuredLogViewer.Search searcher;
    public static IEnumerable<BaseNode> searchResults;
    public static string SearchKeyword;

    /// <summary>
    /// Fired when a user click on a search element.
    /// Scrolls to the selected element in the file tree
    /// </summary>
    /// <param name="elementId"> html id of the node to scroll to</param>
    /// <returns>task to update the app</returns>
    private ValueTask<bool> ScrollToElementId(string elementId)
    {
        return JSRuntime.InvokeAsync<bool>("scrollToElementId", elementId);
    }

    /// <summary>
    /// Fired when a user clicks on a search result
    /// Scrolls to/highlights the elemend in the file tree
    /// </summary>
    /// <param name="args"> Holds the event (.Value is the node selected) </param>
    /// <returns> Task to update app </returns>
    async System.Threading.Tasks.Task OnChange(TreeEventArgs args)
    {
        BaseNode org = ((ProxyNode)(args.Value)).Original;
        org.IsSelected = true;
        foreach (var node in org.GetParentChainExcludingThis())
        {
            node.IsExpanded = true;
        }
        ContainerSplit.searching = true;
        string[] elem = new string[1];
        elem[0] = org.Id.ToString();

        //repeated lines to solve render bug where properties not updating in time to be rendered
        await System.Threading.Tasks.Task.Delay(1000);
        ContainerSplit.Render();
        ContainerSplit.searching = true;
        org.IsSelected = true;
        ContainerSplit.Render();
        await System.Threading.Tasks.Task.Delay(1000);
        await JSRuntime.InvokeVoidAsync("blazorHelpers.scrollToFragment", elem);
    }

    /// <summary>
    /// Fired when a node is clicked to expand it
    /// Shows the children
    /// </summary>
    /// <param name="args"> Holds the event (.Value is the node selected) </param>
    public static void ExpandResults(TreeExpandEventArgs args)
    {
        TreeFormatting.OnExpand(args);
        if (args.Value is TreeNode && ((TreeNode)(args.Value)).HasChildren)
        {
            args.Children.Expanded = (obj) => (obj is TreeNode && ((TreeNode)obj).HasChildren);
        }
    }

    /// <summary>
    /// Fired when text is entered into the search box
    /// Displays the search results
    /// </summary>
    /// <param name="value"> Value to be searching for </param>
    /// <param name="name"> Name of textbox where value was written </param>
    void Change(string value, string name)
    {
        SearchKeyword = value;
        if (searcher == null)
        {
            Console.WriteLine(String.IsNullOrEmpty(SearchKeyword));
            this.StateHasChanged();
            return;
        }
        IEnumerable<StructuredLogViewer.SearchResult> results = searcher.FindNodes(value, new System.Threading.CancellationToken());
        searchResults = BuildResultTree(results) as IEnumerable<BaseNode>;
        this.StateHasChanged();
    }

    /// <summary>
    /// Builds a tree from the results searched for
    /// </summary>
    /// <param name="resultsObject"> results to form a tree from </param>
    /// <returns> The created tree </returns>
    public IEnumerable BuildResultTree(object resultsObject)
    {
        var results = resultsObject as ICollection<StructuredLogViewer.SearchResult>;
        if (results == null)
        {
            return results;
        }

        var root = new Folder();

        root.Children.Add(new Message
        {
            Text = $"{results.Count} result{(results.Count == 1 ? "" : "s")}."
        });

        bool includeDuration = results.Any(r => r.Duration != default);

        if (includeDuration)
        {
            results = results.OrderByDescending(r => r.Duration).ToArray();
        }

        foreach (var result in results)
        {
            TreeNode parent = root;

            if (!includeDuration)
            {
                var project = result.Node.GetNearestParent<Project>();
                if (project != null)
                {
                    var projectProxy = root.GetOrCreateNodeWithName<ProxyNode>(project.Name);
                    projectProxy.Original = project;
                    if (projectProxy.Highlights.Count == 0)
                    {
                        projectProxy.Highlights.Add(project.Name);
                    }

                    parent = projectProxy;
                    parent.IsExpanded = true;
                }

                var target = result.Node.GetNearestParent<Target>();
                if (target != null)
                {
                    var targetProxy = parent.GetOrCreateNodeWithName<ProxyNode>(target.TypeName + " " + target.Name);
                    targetProxy.Original = target;
                    if (targetProxy.Highlights.Count == 0)
                    {
                        targetProxy.Highlights.Add(targetProxy.Name);
                    }

                    parent = targetProxy;
                    parent.IsExpanded = true;
                }
                // nest under a Task, unless it's an MSBuild task higher up the parent chain
                var task = result.Node.GetNearestParent<Microsoft.Build.Logging.StructuredLogger.Task>(t => !string.Equals(t.Name, "MSBuild", StringComparison.OrdinalIgnoreCase));
                if (task != null)
                {
                    var taskProxy = parent.GetOrCreateNodeWithName<ProxyNode>(task.TypeName + " " + task.Name);
                    taskProxy.Original = task;
                    if (taskProxy.Highlights.Count == 0)
                    {
                        taskProxy.Highlights.Add(taskProxy.Name);
                    }

                    parent = taskProxy;
                    parent.IsExpanded = true;
                }
            }

            var proxy = new ProxyNode();
            proxy.Original = result.Node;
            proxy.SearchResult = result;
            parent.Children.Add(proxy);
        }

        if (!root.HasChildren)
        {
            root.Children.Add(new Message { Text = "No results found." });
        }

        return root.Children;
    }
}
